﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Collections;

namespace StoneFish
{
    public class TextHider
    {
        
        RandomGenerator RndmGenX, RndmGenY;                 
        List<Point> GeneratedPoints;                        
        Bitmap OrgImage;
        static string DUPLICATE_POINT_MSG= " duplicate points have been generated, reduce message length or try another key";
        static string NO_HIDDEN_TEXT_MSG = "No hidden text to display";
        static string SHORT_KEY_MSG = "the key is must be 6-digits long";
        /// <summary>
        /// A constructor method to initialise the container with the image and the key 
        /// </summary>
        public TextHider(Bitmap Img, string Key)
        {
            try
            {
                int Key1 = Convert.ToInt32(Key.Substring(0, 3));
                int Key2 = Convert.ToInt32(Key.Substring(3, 3));
                RndmGenX = new RandomGenerator(Key1, Img.Width);
                RndmGenY = new RandomGenerator(Key2, Img.Height);
                GeneratedPoints = new List<Point>();
                OrgImage = Img;
            }
            catch (ArgumentOutOfRangeException ex)
            {
                throw new ArgumentOutOfRangeException(SHORT_KEY_MSG, ex);
            }
        }


        ///<summary>
        ///Inserts the secret message in the image file*/
        /// </summary>
        /// <returns>formats the text and hides it inside the image</returns>
        public Bitmap HideText(string PlainText)
        {
           PlainText = PlainText.Encapsulate();
           PlainText = PlainText.ToBitString();
            return EncodeImage(PlainText);
        }


        ///<summary>
        /// Extracts a hidden message from the image file
        /// </summary>
        ///<returns>string representing the extracted text from the image file</returns>
        public string ExtractText()
        {
            try
            {
                string HiddenText = ReadTextHeader();
                if (!HiddenText.isValidStart())
                    throw new InvalidOperationException(NO_HIDDEN_TEXT_MSG);
                while (!(HiddenText.isValidEnd()))
                    HiddenText += ReadChar();
                HiddenText = HiddenText.Decapsulate();
                return HiddenText;
            }
            catch (InvalidOperationException)
            {
                return NO_HIDDEN_TEXT_MSG;
            }
        }


        ///<summary>
        /// retrieves a random coordinates (x,y) from the generators*/
        /// </summary>
        /// <returns>Point object with generated x and y coordinates </returns>
        private Point GeneratePoint()
        {
            
                int pointX = RndmGenX.generate();
                int pointY = RndmGenY.generate();
                Point p = new Point(pointX, pointY);
                if (IsDuplicate(p))
                    throw new InvalidOperationException(DUPLICATE_POINT_MSG);
                GeneratedPoints.Add(p);
                return p;
           
        }

        ///<summary>
        /// checks if the random point was previously generated
        /// </summary>
        ///<returns>Boolean indicating if the point is already generated</returns>
        private bool IsDuplicate(Point NewPoint)   
        {
            Point o = (from s in GeneratedPoints
                       where (s.X == NewPoint.X && s.Y == NewPoint.Y)
                       select s).FirstOrDefault();
            if (o.X == 0 && o.Y == 0) return false; else return true;
        }



        ///<summary>
        ///Enodes each pixel of the image with the new color
        /// </summary>
        ///<returns>Bitmap represnting the new image with the hidden text</returns>
        private Bitmap EncodeImage(string BitString)
        {
            for (int i = 0; i < BitString.Length; i++)
            {
                try
                {
                    Point HidePoint = GeneratePoint();
                    Color clr = OrgImage.GetPixel(HidePoint.X, HidePoint.Y);
                    Color NewClr = EncodeColor(clr, BitString[i]);
                    OrgImage.SetPixel(HidePoint.X, HidePoint.Y, NewClr);
                }
                catch (InvalidOperationException ex)
                {
                    break;
                    throw ex;
                }
            }
            return OrgImage;
        }



        ///<summary>
        ///Encodes a bit inside an image pixel color
        /// </summary>
        /// <returns>A Color object represnting the new color </returns>
        private Color EncodeColor(Color orgColor,char Bit)
        {
            int red = 0;
            if (Bit == '1') red = orgColor.R | 1;
            else red = orgColor.R & 254;
            Color NewClr = Color.FromArgb(orgColor.A, (byte)red, orgColor.G, orgColor.B);
            return NewClr;
        }

        ///<summary>
        ///Reads the first two chars from the image
        /// </summary>
        ///<returns>string representing the header of the hidden text </returns>
        private string ReadTextHeader()
        {
            return ReadChar().ToString() + ReadChar().ToString();
        }



        ///<summary>
        ///Reads a single character from the image
        /// </summary>
        /// <returns> a character of the hidden text</returns>
        private char ReadChar()
        {
            int CharValue = 0;
            for (int i = 0; i < 8; i++)
            {
                try
                {
                    Point newPoint = GeneratePoint();
                    Color clr = OrgImage.GetPixel(newPoint.X, newPoint.Y);
                    byte red = clr.R;
                    CharValue += (red & 1) * (int)Math.Pow(2, (7 - (i % 8)));
                }
                catch (InvalidOperationException duplicateException)
                {
                    break;
                    throw duplicateException;
                }
            }
            return (Convert.ToChar(CharValue));
        }

    }
}
